﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Form1.cs" company="Makoto">
//   Makoto
// </copyright>
// <summary>
//   Main form
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace AY38910
{
    using System;
    using System.ComponentModel;
    using System.Linq;
    using System.Threading;
    using System.Windows.Forms;

    using AY38910.Properties;

    using Timer = System.Threading.Timer;

    /// <summary>
    ///     The main form
    /// </summary>
    public partial class Form1 : Form
    {
        #region Static Fields

        private Timer timer;

        private static WaitHandle handle = new AutoResetEvent(false);

        private static int currentFrame;

        #endregion

        #region Fields

        private object fileObject;

        private ParameterizedThreadStart method;

        /// <summary>
        ///     The serial helper.
        /// </summary>
        private SerialHelper serialHelper;

        /// <summary>
        ///     The thread.
        /// </summary>
        private Thread thread;

        private int delay;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="Form1" /> class.
        /// </summary>
        public Form1()
        {
            this.InitializeComponent();
        }

        #endregion

        #region Delegates

        private delegate void SetProgressCallBack(int current, int total);

        #endregion

        #region Methods

        /// <summary>
        ///     The btn connect_ click.
        /// </summary>
        /// <param name="sender">
        ///     The sender.
        /// </param>
        /// <param name="e">
        ///     The e.
        /// </param>
        private void OnBtnConnectClick(object sender, EventArgs e)
        {
            this.ToggleSerialState();
        }

        private void OnBtnOpenFileClick(object sender, EventArgs e)
        {
            this.openFileDialog1.ShowDialog();
        }

        /// <summary>
        ///     The btn reset_ click.
        /// </summary>
        /// <param name="sender">
        ///     The sender.
        /// </param>
        /// <param name="e">
        ///     The e.
        /// </param>
        private void OnBtnResetClick(object sender, EventArgs e)
        {
            this.Reset();
        }

        private void OnButtonPauseClick(object sender, EventArgs e)
        {
            if (timer != null)
            {
                timer.Change(Timeout.Infinite, Timeout.Infinite);
            }

            this.ResetPsg();
        }

        private void OnButtonPlayClick(object sender, EventArgs e)
        {
            this.PlaySong();
        }

        private void PlaySong()
        {
            if (this.thread == null || this.serialHelper == null)
            {
                return;
            }

            if (this.thread.ThreadState == ThreadState.Aborted || (this.thread.ThreadState == ThreadState.Stopped && this.timer == null))
            {
                this.thread = new Thread(this.method);
                // this.thread.Start(this.fileObject);
            }

            if (this.thread.ThreadState == ThreadState.Unstarted) // || this.thread.ThreadState == ThreadState.Stopped || this.thread.ThreadState == ThreadState.Suspended)
            {
                this.thread.Start(this.fileObject);
            }

            if (this.timer != null)
            {
                this.timer.Change(0, 17); // this.delay);
            }
        }

        private void OnFormClosing(object sender, FormClosingEventArgs e)
        {
            this.Reset();

            if (this.serialHelper != null)
            {
                this.serialHelper.Dispose();
            }
        }

        /// <summary>
        ///     The form 1_ load.
        /// </summary>
        /// <param name="sender">
        ///     The sender.
        /// </param>
        /// <param name="e">
        ///     The e.
        /// </param>
        private void OnFormLoad(object sender, EventArgs e)
        {
            this.cboSerialPorts.DataSource = SerialHelper.ListPortNames();
        }

        private void OnOpenFileDialogOk(object sender, CancelEventArgs e)
        {
            this.OpenFile(this.openFileDialog1.FileName);
        }

        private void OpenFile(string fileName)
        {
            this.Reset();

            var lastDot = fileName.LastIndexOf('.');
            var ext = fileName.Substring(lastDot + 1).ToUpper();
            switch (ext)
            {
                case "VTX":
                    break;
                case "AY":
                    {
                        var reader = new AYFileReader();
                        AyFile file = reader.Read(fileName);
                        this.thread = new Thread(this.PlayAyFile);
                        this.thread.Start(file);
                    }
                    break;
                case "MID":
                    // TODO : Implement
                    break;
                case "YM":
                    {
                        var reader = new YmFileReader();
                        var ymFile = reader.Read(fileName);
                        this.tbxTitle.Text = ymFile.SongName;
                        this.tbxAuthor.Text = ymFile.AuthorName;
                        this.tbxComments.Text = ymFile.SongComment;
                        this.trackBar1.Maximum = (int)ymFile.FrameCount;
                        this.delay = (int)(((float)1 / ymFile.FrameSpeed) * 1000);

                        this.method = this.PlayYmFile;
                        this.thread = new Thread(this.method);
                        this.fileObject = ymFile;

                        break;
                    }
            }

            this.PlaySong();
        }

        private void OnTrackBarScroll(object sender, EventArgs e)
        {
            currentFrame = this.trackBar1.Value;
        }

        private void PlayAyFile(object file)
        {
            var ayfile = (AyFile)file;
            if (ayfile.Song != null && ayfile.Song.Count > 0)
            {
            }
        }

        private void PlayYmFile(object file)
        {
            var ymFile = file as YmFile;
            if (ymFile == null)
            {
                return;
            }

            currentFrame = 0;

            if (this.timer != null)
            {
                // If there's still an active timer, stop it and dispose it.
                this.ResetTimer();
            }

            var previousData = new byte[14];

            this.SetProgress(0, (int)ymFile.FrameCount);

            this.timer = new Timer(
                    state =>
                    {
                        var autoEvent = (AutoResetEvent)state;
                        if (currentFrame >= ymFile.FrameCount)
                        {
                            if (this.timer != null)
                            {
                                autoEvent.Set();
                                this.ResetTimer();
                            }

                            this.ResetPsg();

                            return;
                        }

                        var frame = ymFile.Frames[currentFrame];

                        var data = new[]
                                   {
                                       frame.VoiceAPeriodCourse,
                                       (byte)(frame.VoiceAPeriodFine & 0x0f),
                                       frame.VoiceBPeriodCourse,
                                       (byte)(frame.VoiceBPeriodFine & 0x0f),
                                       frame.VoiceCPeriodCourse,
                                       (byte)(frame.VoiceCPeriodFine & 0x0f),
                                       (byte)(frame.NoisePeriod & 0x1f),
                                       frame.MixerControl,
                                       (byte)(frame.VolumeControlA & 0x3f),
                                       (byte)(frame.VolumeControlB & 0x3f),
                                       (byte)(frame.VolumeControlC & 0x3f),
                                       frame.EnvelopeCourse,
                                       frame.EnvelopeFine,
                                       (byte)(frame.EnvelopeShape & 0x0f)
                                   };

                        if (previousData.Zip(data, (p, d) => p != d).Any(x => x))
                        {
                            this.serialHelper.Write(data);
                        }

                        previousData = data;
                        this.SetProgress(currentFrame, (int)ymFile.FrameCount);

                        Interlocked.Increment(ref currentFrame);
                    },
                    handle,
                    0,
                    delay);


        }

        private void ResetTimer()
        {
            if (this.timer != null)
            {
                this.timer.Change(Timeout.Infinite, Timeout.Infinite);
                this.timer.Dispose();
                this.timer = null;
            }
        }

        /// <summary>
        ///     The reset.
        /// </summary>
        private void Reset()
        {
            this.trackBar1.Value = 0;
            this.ResetTimer();

            if (this.thread != null && this.thread.IsAlive)
            {
                this.thread.Abort();
            }

            this.ResetPsg();
        }

        private void ResetPsg()
        {
            if (this.serialHelper != null)
            {
                // Set all registers to 0
                var resetBytes = new byte[14];
                this.serialHelper.Write(resetBytes);
            }
        }

        private void SetProgress(int current, int total)
        {
            if (this.trackBar1.InvokeRequired)
            {
                SetProgressCallBack callBack = this.SetProgress;
                try
                {
                    this.Invoke(callBack, current, total);
                }
                catch (ThreadInterruptedException)
                {
                    // Do nothing
                }
            }
            else
            {
                var progress = (int)(((float)current / total) * total);
                try
                {
                    this.trackBar1.Value = progress;
                }
                catch (ArgumentException)
                {
                }
            }
        }

        private void ToggleSerialState()
        {
            if (this.serialHelper != null)
            {
                // Already connected, therefore we will dispose of the current instance and set our state to "Disconnected"
                this.serialHelper.Dispose();
                this.serialHelper = null;
                this.toolStripStatusLabel1.Text = Resources.Disconnected;
                this.btnConnect.Text = Resources.Connnect;
                this.groupBox1.Enabled = false;
            }
            else
            {
                // Use the serial port that was chosen in the drop down and connect to it.               
                if (this.cboSerialPorts.SelectedItem != null)
                {
                    string portName = this.cboSerialPorts.SelectedItem.ToString();
                    this.serialHelper = new SerialHelper(portName);
                    this.toolStripStatusLabel1.Text = string.Format("Connected to {0}", portName);
                    this.btnConnect.Text = Resources.Disconnnect;
                    this.groupBox1.Enabled = true;
                }
                else
                {
                    MessageBox.Show(Resources.InvalidSerialPort);
                }
            }
        }

        #endregion

        private void OnDragDrop(object sender, DragEventArgs e)
        {
            var files = e.Data.GetData(DataFormats.FileDrop) as string[];
            if (files != null)
            {
                this.OpenFile(files[0]);
            }
        }

        private void OnDragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Copy;
        }
    }
}